package org.zebra.logging.util;

import cn.hutool.core.lang.Dict;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.zebra.logging.enums.BusinessStatus;
import org.zebra.logging.enums.BusinessType;
import org.zebra.logging.event.OperLogEvent;
import org.zebra.logging.handle.LogSpelProcess;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class LoggingUtil {

    private static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};

    private static final LogSpelProcess logSpelProcess = new LogSpelProcess();

    public static void doBefore() {
        Dict logDic = Dict.create().set("operLogId", IdUtil.getSnowflakeNextId());
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        Objects.requireNonNull(requestAttributes).setAttribute("$log", logDic, RequestAttributes.SCOPE_REQUEST);
    }

    public static void doAfter(
            String title,
            String module,
            BusinessType businessType,
            Object currentObject,
            Method method,
            Object[] args,
            Object result,
            boolean isSaveRequestData,
            boolean isSaveResponseData,
            String[] excludeParamNames,
            Exception exception) {
        try {
            // *========数据库日志=========*//
            OperLogEvent operLog = new OperLogEvent();
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            Dict logDic = (Dict)
                    Objects.requireNonNull(requestAttributes).getAttribute("$log", RequestAttributes.SCOPE_REQUEST);
            // Dict logDic = (Dict) ThreadLocalUtil.$get().getObj("$log");
            operLog.setId(logDic.getLong("operLogId"));
            operLog.setModuleName(module);
            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
            // 请求的地址
            HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
            String ip = cn.hutool.extra.servlet.ServletUtil.getClientIP(httpServletRequest);
            operLog.setOperIp(ip);
            operLog.setOperUrl(CharSequenceUtil.sub(httpServletRequest.getRequestURI(), 0, 255));
            operLog.setCreateTime(new Date());
            if (exception != null) {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setMsg(CharSequenceUtil.sub(exception.getMessage(), 0, 2000));
            }
            // 设置方法名称
            String className = currentObject.getClass().getName();
            operLog.setMethodName(className + "." + method.getName() + "()");
            // 设置请求方式
            operLog.setRequestMethod(httpServletRequest.getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(
                    httpServletRequest,
                    method,
                    args,
                    currentObject,
                    title,
                    businessType,
                    isSaveRequestData,
                    isSaveResponseData,
                    excludeParamNames,
                    operLog,
                    result);
            // 发布事件保存数据库

            // 如果存在代表有开放数据审计
            if (logDic.containsKey("audit")) {
                List<Dict> authList = (List<Dict>) logDic.get("audit");
                for (Dict authDict : authList) {
                    OperLogEvent.AuditData auditData = new OperLogEvent.AuditData();
                    auditData.setType(authDict.getInt("dataAuditOperation"));
                    auditData.setTableName(authDict.getStr("dataAuditTableName"));
                    auditData.setChangeData(authDict.getStr("dataAuditChangedData"));
                    auditData.setTitle(authDict.getStr("dataAuditTitle"));
                    auditData.setCreateTime(operLog.getCreateTime());
                    auditData.setId(IdUtil.getSnowflakeNextId());
                    auditData.setLogId(operLog.getId());
                    operLog.getAuditDataList().add(auditData);
                }
            }
            SpringUtil.getApplicationContext().publishEvent(operLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("异常信息:{}", exp.getMessage());
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log     日志
     * @param operLog 操作日志
     * @param result  结果对象
     * @throws Exception
     */
    /**
     * @param httpServletRequest
     * @param method             方法
     * @param args               参数
     * @param currentObject      当前对象
     * @param title              标题
     * @param businessType       业务类型
     * @param isSaveRequestData  是否保存请求数据
     * @param isSaveResponseData 是否保存响应数据
     * @param excludeParamNames  排除参数
     * @param operLog            操作事件
     * @param result             结果
     * @throws Exception
     */
    private static void getControllerMethodDescription(
            HttpServletRequest httpServletRequest,
            Method method,
            Object[] args,
            Object currentObject,
            String title,
            BusinessType businessType,
            boolean isSaveRequestData,
            boolean isSaveResponseData,
            String[] excludeParamNames,
            OperLogEvent operLog,
            Object result)
            throws Exception {
        // 设置action动作
        operLog.setBusinessType(businessType.ordinal());
        // 设置标题
        if (title.isEmpty()) {
            String status = operLog.getStatus() == 0 ? "成功" : "失败";
            title = businessType.getMsg() + status;
        } else {
            String status = operLog.getStatus() == 0 ? "成功" : "失败";
            title = dealStr(title, method, args, currentObject) + status;
        }
        operLog.setTitle(title);

        // 是否需要保存request，参数和值
        if (isSaveRequestData) {
            // 获取参数的信息，传入到数据库中。
            setRequestValue(httpServletRequest, args, operLog, excludeParamNames);
        }
        // 是否需要保存response，参数和值
        if (isSaveResponseData && ObjectUtil.isNotNull(result)) {
            operLog.setJsonResult(CharSequenceUtil.sub(JSONUtil.toJsonStr(result), 0, 2000));
        }
    }

    private static void setRequestValue(
            HttpServletRequest httpServletRequest,
            Object[] paramsArray,
            OperLogEvent operLog,
            String[] excludeParamNames)
            throws Exception {
        Map<String, String> paramsMap = ServletUtil.getParamMap(httpServletRequest);
        String requestMethod = operLog.getRequestMethod();
        if (MapUtil.isEmpty(paramsMap)
                && (HttpMethod.PUT.name().equals(requestMethod)
                        || HttpMethod.POST.name().equals(requestMethod))) {
            String params = argsArrayToString(paramsArray, excludeParamNames);
            operLog.setOperParam(StrUtil.sub(params, 0, 2000));
        } else {
            MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
            MapUtil.removeAny(paramsMap, excludeParamNames);
            operLog.setOperParam(StrUtil.sub(JSONUtil.toJsonStr(paramsMap), 0, 2000));
        }
    }

    private static String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
        StringJoiner params = new StringJoiner(" ");
        if (ArrayUtil.isEmpty(paramsArray)) {
            return params.toString();
        }
        for (Object o : paramsArray) {
            if (ObjectUtil.isNotNull(o)) {
                String str = JSONUtil.toJsonStr(o);
                Dict dict = JSONUtil.toBean(str, Dict.class);
                if (MapUtil.isNotEmpty(dict)) {
                    MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
                    MapUtil.removeAny(dict, excludeParamNames);
                    str = JSONUtil.toJsonStr(dict);
                }
                params.add(str);
            }
        }
        return params.toString();
    }

    private static String dealStr(String expression, Method method, Object[] args, Object currentObject) {
        String content = "";
        List<String> templates = new ArrayList<>();
        // 当前表达式存在 SPEL，会覆盖 value 的值
        if (CharSequenceUtil.isNotBlank(expression)) {
            templates.add(expression);
            HashMap<String, String> processMap =
                    logSpelProcess.processBeforeExec(templates, method, args, currentObject);
            content = processMap.get(expression);
        }
        return content;
    }
}
